package com.onpositive.mediawiki.propertyparser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import storage.v1.ValuesExtractorV1;

import com.onpositive.data.units.Unit;
import com.onpositive.data.units.Units;
import com.onpositive.semantic.model.api.meta.IMeta;
import com.onpositive.semantic.model.api.property.IPropertyProvider;
import com.onpositive.units.ParsedDimension;
import com.onpositive.units.ParsedScalar;

public class DimensionParser extends RangeParser<ParsedDimension> {

	public DimensionParser() {
		super( ParsedDimension.class, ParsedDimension.ID ) ;
	}
	
	@Override
	protected ArrayList<Block<ParsedDimension>> parseNonRangeValues(
													String str,
													int startIndex,
													int endIndex,
													int delimeterEnd,
													IMeta meta,
													IPropertyProvider propertyProvider,
													Map<String, Object> metaInfo,
													String comment )
	{
		ArrayList<Block<Double>> numBlockList = new ArrayList<Block<Double>>() ;
		
		Block<Double> lBlock ;
		int off = startIndex ;
		while(true){
			try{
				lBlock = lookNearestNumberFromStart( str, off, endIndex ) ;
			}catch(Exception e){
				e.printStackTrace() ;
				break ;
			}
			if( lBlock == null )
				break ;
			
			off = lBlock.getEnd() ;
			numBlockList.add(lBlock) ;
		}
		if( numBlockList.isEmpty() )
			return null ;
			
		ArrayList<Block<ParsedDimension>> result = new ArrayList<Block<ParsedDimension>>() ;
		String unitKind = ValuesExtractorV1.extractUnitKind(meta) ;
		
		int ub, lb ;
		final int size = numBlockList.size();
		Block<ParsedDimension> prevBlock = null ;
		for( int i = 0 ; i < size ; i++ )
		{
			Block<Double> numBlock = numBlockList.get(i) ;			

			lb = numBlock.getEnd() ;
			ub = ( i == size-1 ) ? endIndex : numBlockList.get(i+1).getStart() ;			
			Block<Unit> unitBlock = lookUnitInTheBeginning( str, lb, ub, unitKind);

			if( unitBlock == null ){
				lb = prevBlock != null ? prevBlock.getEnd() : (i==0) ? startIndex : numBlockList.get(i-1).getEnd() ;
				ub = numBlock.getStart() ;
				unitBlock = lookUnitInTheEnd(str, lb, numBlock.getStart(), unitKind);
			}
			
			if( unitBlock == null ){
				prevBlock = null ;
				continue ;
			}
			
			int start = Math.min( numBlock.getStart(), unitBlock.getStart() ) ;
			int end   = Math.max( numBlock.getEnd(), unitBlock.getEnd() ) ;

			String newComment = getDefaultComment(comment) ;
			ParsedDimension pDim = new ParsedDimension( numBlock.getVal(), unitBlock.getVal(), newComment ) ;
			Block<ParsedDimension> blk = new Block<ParsedDimension>(str, start, end, pDim) ;
			result.add(blk) ;
			
			numBlock.putMetaInfo("processed", true ) ;
			numBlock.putMetaInfo("valBlock", blk ) ;
			
			prevBlock = blk ;
		}

		ArrayList<Block<ParsedDimension>> defaultReturnValue = result.isEmpty() ? null : result;
		Unit unit = null ;
		try{
			unit = (Unit) metaInfo.get( "unit" );			
		} finally{
			if( unit == null )				
				return defaultReturnValue ;
		}
		
		
		ArrayList<Block<Double>> queue = new ArrayList<Block<Double>>() ; 
		if( startIndex == 0 ){
			Block<Double> firstBlock = numBlockList.get(0);
			firstBlock.putMetaInfo("position", "firstBlock" ) ;
			queue.add( firstBlock ) ;
		}
		
		if( endIndex == str.length() ) {
			Block<Double> lastBlock = numBlockList.get(size-1);
			lastBlock.putMetaInfo("position", "lastBlock" ) ;
			queue.add( lastBlock ) ;
		}
		
		if( queue.isEmpty() )
			return defaultReturnValue ;
		
		for( Block<Double> numBlock : queue ){
			
			try{
				Boolean processed = numBlock.getMetaInfo("processed", Boolean.class ) ;
				if( processed != null && processed )
					continue ;
			}
			finally{}
			
			//do we have Unit of some other kind associated with the numBlock ?
			Block<Unit> uBlock = lookUnitInTheBeginning(str, numBlock.getEnd(), endIndex, null ) ;
			if( uBlock != null )
				continue ;
			
			String position = numBlock.getMetaInfo( "position", String.class ) ;
			ParsedScalar pc = new ParsedScalar( numBlock.getVal(), "") ;
			Block<ParsedScalar> pcBlock = new Block<ParsedScalar>(str, numBlock.getStart(), numBlock.getEnd(), pc) ;
			metaInfo.put(position, pcBlock) ;
//			
//			String newComment = getDefaultComment(comment) ;
//			ParsedDimension pDim = new ParsedDimension( numBlock.getVal(), unit, newComment ) ;
//			Block<ParsedDimension> blk = new Block<ParsedDimension>( str, numBlock.getStart(), numBlock.getEnd(), pDim) ;
//			numBlock.putMetaInfo("processed", true ) ;
//			numBlock.putMetaInfo("valBlock", blk ) ;

		}
		return defaultReturnValue ;
		
//		result.clear() ;
//		for( Block<Double> numBlock : numBlockList ){
//			try{
//				Block<ParsedDimension> blk = numBlock.getMetaInfo("valBlock", Block.class ) ;
//				if( blk != null )
//					result.add(blk) ;
//			}finally{}								
//		}		
//		return result ;

	}


	
	@Override
	protected Block<ParsedDimension> parseRangeValue( String str, int[] bounds, IMeta meta,
													  IPropertyProvider propertyProvider,
													  Map<String, Object> metaInfo,
													  String comment)
	{		
		final int llb = bounds[0] ;
		final int  lb = bounds[1] ;
		final int  ub = bounds[2] ;
		final int uub = bounds[3] ;
		
		Block<Double> uBlock = null , lBlock = null ; 
		{
			lBlock = lookNearestNumberFromEnd(str, llb, lb);
			if( lBlock == null )
				return null;		
			
			uBlock = lookNearestNumberFromStart(str, ub, uub);
			if( uBlock == null )
				return null;			
		}
		Unit unit = null ;
		
		final int ube = uBlock.getEnd() ;
		final int ubs = uBlock.getStart() ;
		final int lbe = lBlock.getEnd() ;
		final int lbs = lBlock.getStart() ;
		
		String defaultComment = getDefaultComment(comment) ;
		
//		try{
//			unit = (Unit) metaInfo.get("unit") ;
//		}catch( Exception e ){
//			unit = null ;
//		}
//		if( unit != null ){
//			double lValue = lBlock.getVal() ;
//			double uValue = uBlock.getVal() ;
//			if( str.substring(lbe,lb).trim().length() == 0 && str.substring(ub,ubs).trim().length() == 0 ){
//				ParsedDimension pDim = new ParsedDimension(lValue, uValue, unit, defaultComment );
//				return new Block<ParsedDimension>( str, lbs, ube, pDim );
//			}
//			else
//				return null ;
//		}

		int start = Integer.MAX_VALUE, end = Integer.MIN_VALUE ;
		int lpEnd = Integer.MIN_VALUE, upStart = Integer.MAX_VALUE ;
		
		String unitKind = ValuesExtractorV1.extractUnitKind( meta );
		ParsedDimension lowerBoundValue = null, upperBoundValue = null ;
		
		Block<Double> llBlock = lookNearestNumberFromEnd(str, llb, lbs);
		if( llBlock != null )
		{
			String supposedUnitString = str.substring( llBlock.getEnd() , lbs ).trim() ;
			Unit auxUnit = Units.getUnitByShortName(supposedUnitString, unitKind ) ;
			if( auxUnit != null ){
				
				Unit unit0 = null, unit1=null ;
				if( str.substring( lbe, lb).trim().length() == 0 )
				{
					unit1 = auxUnit ;
					Block<Unit> blk = lookUnitInTheEnd(str, 0, llBlock.getStart(), unitKind);
					lpEnd = lbe ;
					if( blk != null ){
						unit0 = blk.getVal() ;
						if( Units.areCongruent( unit0, unit1 ) )
							start = blk.getStart() ;
						else
							unit0 = null ;						
					}
				}
				else{
					unit0 = auxUnit ;
					Block<Unit> blk = lookUnitInTheBeginning(str, lbe, lb,	unitKind);					
					if( blk != null ){
						lpEnd = blk.getEnd() ;
						unit1 = blk.getVal() ;
						if( Units.areCongruent( unit0, unit1 ) )
							start = llBlock.getStart() ;
						else
							unit0 = null ;
					}
				}
				if( unit0 != null && unit1 != null ){
					
					ParsedDimension pd0 = new ParsedDimension( llBlock.getVal(), unit0, null) ;
					ParsedDimension pd1 = new ParsedDimension(  lBlock.getVal(), unit1, null) ;
					lowerBoundValue = ParsedDimension.appendValue( pd0, pd1, unit0, this ) ; 
				}
			}
		}
		
		Block<Double> uuBlock = lookNearestNumberFromStart(str, ube, uub);
		if( uuBlock != null )
		{
			String supposedUnitString = str.substring( ube, uuBlock.getStart() ).trim() ;
			Unit auxUnit = Units.getUnitByShortName(supposedUnitString, unitKind ) ;
			if( auxUnit != null ){
				
				Unit unit0 = null, unit1=null ;
				if( str.substring( ub, ubs).trim().length() == 0 )
				{
					unit0 = auxUnit ;
					Block<Unit> blk = lookUnitInTheBeginning(str, uuBlock.getEnd(), str.length(), unitKind);
					upStart = ubs ;
					if( blk != null ){
						unit1 = blk.getVal() ;
						if( Units.areCongruent( unit0, unit1 ) )
							end = blk.getEnd() ;
						else
							unit0 = null ;						
					}
				}
				else{
					unit1 = auxUnit ;
					Block<Unit> blk = lookUnitInTheEnd(str, ub, ubs, unitKind);
					if( blk != null ){
						upStart = blk.getStart() ;
						unit0 = blk.getVal() ;
						if( Units.areCongruent( unit0, unit1 ) )
							end = uuBlock.getEnd() ;
						else
							unit0 = null ;
					}
				}
				if( unit0 != null && unit1 != null ){
					
					ParsedDimension pd0 = new ParsedDimension(  uBlock.getVal(), unit0, null) ;
					ParsedDimension pd1 = new ParsedDimension( uuBlock.getVal(), unit1, null) ;
					upperBoundValue = ParsedDimension.appendValue( pd0, pd1, unit0, this ) ; 
				}
			}
		}		
		
		double lValue = lBlock.getVal(), uValue = uBlock.getVal() ;
		
		if( lowerBoundValue != null ){
			if( str.substring(lpEnd, lb).trim().length() != 0 )
				return null ;
			
			lValue = (Double) lowerBoundValue.value() ;
			unit = lowerBoundValue.getUnit().getPrimaryUnit() ;
		}
		if( upperBoundValue != null ){
			if( str.substring(ub, upStart).trim().length() != 0 )
				return null ;
			if (lowerBoundValue==null){
				lowerBoundValue=upperBoundValue;
			}
			uValue = (Double) upperBoundValue.value() ;
			unit = lowerBoundValue.getUnit().getPrimaryUnit() ;
		}
		
		Unit upperUnit = null, lowerUnit = null ;
		if( unit == null ){			
			Block<Unit> unitBlock = null ;
			
			//upper
			unitBlock = lookUnitInTheBeginning(str, ube, uub, unitKind);
			if( unitBlock == null )				
				unitBlock = lookUnitInTheEnd(str, ub, ubs, unitKind);			
			
			if( unitBlock != null ){
				upperUnit = unitBlock.getVal() ;			
				end = Math.max( Math.max(end, unitBlock.getEnd() ), ube ) ;
				upStart = Math.min(Math.min(upStart, ubs), unitBlock.getStart()) ;
			}
			else
				upStart = ubs ;
			
			//lower
			//TODO sneaky hack here
			unitBlock = lookUnitInTheEnd(str, llb, lbs, unitKind);

			if( unitBlock == null )
				unitBlock = lookUnitInTheBeginning(str, lbe, lb, unitKind);				

			if( unitBlock != null ){
				lowerUnit = unitBlock.getVal() ;			
				start = Math.min( Math.min(start, unitBlock.getStart() ), lbs ) ;
				lpEnd = Math.max(Math.max(lpEnd, lbe), unitBlock.getEnd() ) ;
			}
			else
				lpEnd = lbe ;
			
			
			if( lowerUnit != null || upperUnit != null ){				
			
				if( upperUnit == null )
					unit = lowerUnit ;
				else if( lowerUnit == null )
					unit = upperUnit ;
				else if ( lowerUnit == upperUnit )
					unit = lowerUnit ;
				else{
					//TODO actualy, here we should convert to "elder" Unit, not to Primary
					unit = lowerUnit.getPrimaryUnit() ;
					lValue = Units.convertToPrimary( lBlock.getVal(), lowerUnit ) ;
					uValue = Units.convertToPrimary( uBlock.getVal(), upperUnit ) ;
				}
			}
		}
		
		if( str.substring(lpEnd, lb).trim().length() != 0 || str.substring(ub, upStart).trim().length() != 0 )
			return null ;
		
		if( unit == null ){
			
			try{
				unit = (Unit) metaInfo.get( "unit" );			
			} finally{
				if( unit == null )				
					return null ;
			}			
			
			Object obj = metaInfo.get("singleRange") ;
			if( obj != null ){
				metaInfo.put("singleRange", false ) ;
				return null;
			}
			
			Block<Unit> otherUnit = lookUnitInTheBeginning(str, ube, uub, null) ;
			if( otherUnit != null )
				return null ;
			
			ParsedScalar pc ;
			if( isFakeRange(str, lb, ub, lValue, uValue) )
				pc = new ParsedScalar(lValue+uValue, "") ;
			else
				pc = new ParsedScalar(lValue, uValue, "") ;
			
			Block<ParsedScalar> blk = new Block<ParsedScalar>(str, lbs, ube, pc) ;
			metaInfo.put("singleRange", blk ) ;
			return null ;			
		}
		
		ParsedDimension pDim = null ;
		if( isFakeRange(str, lb, ub, lValue, uValue) )
				pDim = new ParsedDimension(  lValue + uValue, unit, defaultComment );
		else
			pDim = new ParsedDimension(lValue, uValue, unit, defaultComment );
		
		start = Math.min(start, lbs) ;
		end = Math.max(end, ube) ;		
		return new Block<ParsedDimension>( str, start, end, pDim );
	}

	private boolean isFakeRange(String str, int lb, int ub, double lValue,
			double uValue) {
		return lValue > uValue && str.substring(lb, ub).equals("-");
	}
	
private static String[] specialBlocksPositions = new String[]{"singleRange", "firstBlock","lastBlock"};
	
	@Override
	protected void performSpecialProcessing(
			String str,
			ArrayList<Block<ParsedDimension>> blockList,
			IMeta meta,
			IPropertyProvider propertyProvider,
			Map<String, Object> metaInfo,
			String comment )
	{		
		Unit unit ;
		try{
			unit = (Unit) metaInfo.get("unit") ;
		}catch( Exception e ){
//			e.printStackTrace() ;
			return ;
		}
		if( unit == null )
			return ;
		HashMap<String,Block<ParsedScalar>> blockMap = new HashMap<String, Block<ParsedScalar>>() ; 
		
		for( String s : specialBlocksPositions ){
		Object blockObj = metaInfo.get(s) ;
			if( blockObj != null ){
				
				if( blockObj instanceof Boolean )
					continue ;
				
				try{
					Block<ParsedScalar> blk = (Block<ParsedScalar>) blockObj ;
					blk.putMetaInfo("position", s) ;
					blockMap.put(s, blk) ;
				}catch ( Exception e){
					e.printStackTrace() ;					
				}				
			}
		}
		
		Block<ParsedScalar> pcBlock = blockMap.get("singleRange") ;
		
		if( pcBlock != null && blockList.isEmpty() ){
			ParsedDimension pDim ;
			ParsedScalar pc = pcBlock.getVal() ;
			if( pc.isRange() )
				pDim = new ParsedDimension( (Double)pc.getLowerBound(), (Double)pc.getUpperBound(), unit, comment) ;
			else
				pDim = new ParsedDimension( (Double)pc.value(), unit, comment) ;
			Block<ParsedDimension> newBlock = new Block<ParsedDimension>(str,pcBlock.getStart(),pcBlock.getEnd(),pDim);
			newBlock.putMetaInfo( META_KEY_LEVEL_RESTRICTIONS, 0 ) ;
			blockList.add( newBlock ) ;
			return ;
		}
		
		pcBlock = blockMap.get("lastBlock") ;
		if( pcBlock != null && !intersects( pcBlock, blockList ) ){
			ParsedDimension pDim ;
			ParsedScalar pc = pcBlock.getVal() ;
			if( pc.isRange() )
				pDim = new ParsedDimension( (Double)pc.getLowerBound(), (Double)pc.getUpperBound(), unit, comment) ;
			else
				pDim = new ParsedDimension( (Double)pc.value(), unit, comment) ;
			Block<ParsedDimension> newBlock = new Block<ParsedDimension>(str,pcBlock.getStart(),pcBlock.getEnd(),pDim);
			newBlock.putMetaInfo( META_KEY_LEVEL_RESTRICTIONS, 0 ) ;
			blockList.add( newBlock ) ;
			return ;
		}
		
		pcBlock = blockMap.get("firstBlock") ;
		if( pcBlock != null && !intersects( pcBlock, blockList ) ){
			ParsedDimension pDim ;
			ParsedScalar pc = pcBlock.getVal() ;
			if( pc.isRange() )
				pDim = new ParsedDimension( (Double)pc.getLowerBound(), (Double)pc.getUpperBound(), unit, comment) ;
			else
				pDim = new ParsedDimension( (Double)pc.value(), unit, comment) ;
			Block<ParsedDimension> newBlock = new Block<ParsedDimension>(str,pcBlock.getStart(),pcBlock.getEnd(),pDim);
			newBlock.putMetaInfo( META_KEY_LEVEL_RESTRICTIONS, 0 ) ;
			blockList.add( 0 , newBlock ) ;
			return ;
		}		
	}
	

	
	

	@Override
	protected boolean areAlternatives( ParsedDimension val0, ParsedDimension val1 ) {
		
		final boolean isRange = val0.isRange();
		if( isRange != val1.isRange() )
			return false ;
		
		double similarityRate = 0.01;
		if( isRange ){
			double ub0 = (Double) val0.getUpperBound() ;
			double lb0 = (Double) val0.getLowerBound() ;
			double ub1 = (Double) val1.getUpperBound() ;
			double lb1 = (Double) val1.getLowerBound() ;
			
			double epsilon = ( ub0 + lb0 + ub1 + lb1 ) * 0.25 * similarityRate ;
			boolean result = Math.abs( ub0 - ub1 ) < epsilon && Math.abs( lb0 - lb1 ) < epsilon;
			return result ;
		}
		else{
			double v0 = (Double) val0.value() ;
			double v1 = (Double) val1.value() ;
			
			double epsilon = ( v0 + v1 ) * 0.5 * similarityRate ;
			boolean result = Math.abs( v0 - v1 ) < epsilon;
			return result ;			
		}
	}
	
	@Override
	protected ParsedDimension mergeValue( ParsedDimension val0, ParsedDimension val1)
	{
		Unit unit0 = val0.getUnit() ;
		if( unit0 == null )
			return null ;
		
		Unit[] congruentUnits = Units.getCongruentUnits(unit0) ;
		if( congruentUnits == null )
			return null;
					
		Unit unit1 = val1.getUnit() ;
		if( unit1.getKind() == unit0.getKind()  )
		{
			for( Unit _u : congruentUnits )
			{
				if( unit1.equals(_u) && !unit1.equals(unit0) ){
				
					Unit baseUnit = congruentUnits[0] ;
					ParsedDimension newPd = ParsedDimension.appendValue( val0, val1, baseUnit, this ) ;								
					return newPd ;
				}
			}			
		}
		return null ;
	}
	
	@Override
	protected boolean isValid( String str, int delimeterIndex, int pos )
	{
		if( !super.isValid(str, delimeterIndex, pos) )
			return false ;
		
		Block<?> blk = lookNearestNumberFromEnd(str, 0, pos ) ;
		if( blk == null )
			return false ;
		
		String unitString = str.substring(blk.getEnd(), pos).trim() ;
		if( unitString.length() != 0 ){
			if( !Units.isUnit(unitString) )
				return false ;
		}
		
		int numEnd = pos + getRangeDelimeters()[delimeterIndex].length() ;
		blk = lookNearestNumberFromStart(str, numEnd, str.length() ) ;
		
		if( blk == null )
			return false ;
		
		unitString = str.substring( numEnd, blk.getStart() ).trim() ;
		if( unitString.length() != 0 ){
			if( !Units.isUnit(unitString) )
				return false ;
		}
		
		return true ;
	}

		

}
